﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Transactions;
using FastNET.CleanAct.Client.ManagerEngineBusiness.DataObjects;
using FastNET.CleanAct.Common.Configuration.Files.PackageExport;
using FastNET.CleanAct.Common.Core;
using FastNET.CleanAct.Common.Repository;

namespace FastNET.CleanAct.Client.ManagerEngineBusiness.ImportExport
{
    public class PackageUtil
    {

        public struct UnsignResult
        {
            public ExportPackageSettings Settings;
            public X509Certificate2 Certificate;
        }

        private static void ImportRecursePackage(Identity parentID, Identity packageID, TemplateItem child)
        {
            Template template = new Template { IsBuiltIn = child.IsBuiltIn, Name = child.Name, PackageID = packageID };

            template.Save();

            TemplateGroup group = new TemplateGroup { ParentTemplateID = parentID, ChildTemplateID = template.ID };

            group.Save();

            foreach (TemplateItem item in child.Templates)
            {
                ImportRecursePackage(template.ID, packageID, item);
            }

            foreach (TemplateItemAction item in child.Actions)
            {
                TemplateAction action = new TemplateAction
                {
                    ActionType = item.Type,
                    Name = item.Name,
                    Pattern = item.Value,
                    RecursiveMode = (ActionRecursiveMode)item.RecursiveMode,
                    TemplateID = template.ID
                };

                action.Save();
            }
        }

        private static TemplateItem ExportRecursePackage(Identity parentID, Identity packageID)
        {
            Template template = new Template(parentID);

            if (!template.PackageID.HasValue || template.PackageID.Value != packageID)
            {
                return null;
            }

            IList<TemplateGroup> groups = TemplateGroup.Retrieve(parentID);
            IList<TemplateAction> actions = TemplateAction.RetrieveByTemplateID(parentID);
            TemplateItem returnValue = new TemplateItem { Name = template.Name, IsBuiltIn = template.IsBuiltIn };

            //Add the child actions
            foreach (TemplateAction action in actions)
            {
                TemplateItemAction templateItemAction = new TemplateItemAction
                {
                    Name = action.Name,
                    RecursiveMode = ((byte)action.RecursiveMode),
                    Type = action.ActionType,
                    Value = action.Pattern
                };

                returnValue.Actions.Add(templateItemAction);
            }

            //Step down to the next node level
            foreach (TemplateGroup group in groups)
            {
                TemplateItem item = ExportRecursePackage(group.ChildTemplateID, packageID);

                if (item != null)
                {
                    returnValue.Templates.Add(item);
                }
            }

            return returnValue;
        }

        public static void ImportPackage(ExportPackageSettings settings)
        {
            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            using (TransactionScope scope = new TransactionScope())
            {
                Package package = new Package
                {
                    Name = settings.PackageInfo.Name,
                    Publisher = settings.PackageInfo.Publisher,
                    Version = new Version(settings.PackageInfo.Version)
                };

                package.Save();

                foreach (TemplateItem item in settings.Templates)
                {
                    ImportRecursePackage(new Identity(Guid.Empty), package.ID, item);
                }

                scope.Complete();
            }
        }


        private static ExportPackageSettings BuildPackage(Identity packageID)
        {
            Package package = new Package(packageID);
            IList<TemplateGroup> groups = TemplateGroup.Retrieve(new Identity(Guid.Empty));
            ExportPackageSettings settings = new ExportPackageSettings
            {
                PackageInfo =
                {
                    Name = package.Name,
                    Publisher = package.Publisher,
                    Version = package.Version.ToString()
                }
            };

            foreach (TemplateGroup group in groups)
            {
                TemplateItem item = ExportRecursePackage(group.ChildTemplateID, package.ID);

                if (item != null)
                {
                    settings.Templates.Add(item);
                }
            }

            return settings;
        }


        public static void ExportPackage(string fileName, Identity packageID)
        {
            ExportPackageSettings settings = BuildPackage(packageID);

            settings.Save(fileName);
        }


        public static void ExportSignedPackage(string fileName, Identity packageID, X509Certificate2 certificate)
        {
            ExportPackageSettings settings = BuildPackage(packageID);

            string content = ExportPackageSettings.WriteXML(settings);
            byte[] unsignedData = System.Text.Encoding.UTF8.GetBytes(content);

            byte[] signedData = Sign(unsignedData, certificate);

            System.IO.File.WriteAllBytes(fileName, signedData);
        }

        public static UnsignResult UnsignPackage(string fileName)
        {
            byte[] fileContent = System.IO.File.ReadAllBytes(fileName);

            return VerifyAndRemoveSignature(fileContent);
        }


        public static void RemoveOldPackage(string packageName)
        {
            //If anything fails, allow us to roll back
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required,
                                                                 new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
            {

                Package package = Package.Retrieve(packageName);

                IList<Template> templates = Template.RetrieveByPackageID(package.ID);

                foreach (Template template in templates)
                {
                    template.Delete();
                }

                package.Delete();

                scope.Complete();
            }
        }




        private static byte[] Sign(byte[] data, X509Certificate2 signingCert)
        {
            //application/pkcs7-signature

            // create ContentInfo
            ContentInfo content = new ContentInfo(data);

            // SignedCms represents signed data
            SignedCms signedMessage = new SignedCms(content);

            // create a signer
            CmsSigner signer = new CmsSigner(signingCert);

            // sign the data
            signedMessage.ComputeSignature(signer, true);

            // create PKCS #7 byte array
            byte[] signedBytes = signedMessage.Encode();

            // return signed data
            return signedBytes;
        }



        private static UnsignResult VerifyAndRemoveSignature(byte[] data)
        {
            UnsignResult result = new UnsignResult();
            SignedCms signedMessage = new SignedCms();

            // deserialize PKCS #7 byte array
            signedMessage.Decode(data);

            result.Certificate = signedMessage.Certificates[0];

            try
            {
                // check signature
                // false checks signature and certificate
                // true only checks signature
                signedMessage.CheckSignature(true);

                string content = System.Text.Encoding.UTF8.GetString(signedMessage.ContentInfo.Content);

                result.Settings = ExportPackageSettings.LoadXml(content);
            }
            catch (CryptographicException)
            {
                result.Settings = null;
            }

            return result;
        }
    }
}
